import fs from 'fs'
import { app, protocol, BrowserWindow, dialog, ipcMain } from 'electron'
import {
  createProtocol,
  installVueDevtools,
} from 'vue-cli-plugin-electron-builder/lib'

import createApplicationMenu from './menu'
import Note from './Note'

/**
 * 非生产模式，即开发或测试模式
 */
const isDevelopment = process.env.NODE_ENV !== 'production'

/**
 * 保存所有窗口
 */
const windows = new Set<BrowserWindow>()

/**
 * 窗口对应的文件监控器
 */
const openFiles = new Map()

// 在应用创建之前先注册协议
protocol.registerSchemesAsPrivileged([
  { scheme: 'app', privileges: { secure: true, standard: true } },
])

/**
 * 文件信息
 */
interface IFileContent {
  path: string
  content: string
}

/**
 * 创建窗口
 * @return {BrowserWindow} 创建的窗口
 */
function createWindow(): BrowserWindow {
  let x, y

  const currentWindow = BrowserWindow.getFocusedWindow() //获取当前活动的浏览器窗口
  if (currentWindow) {
    let [currentWindowX, currentWindowY] = currentWindow.getPosition()
    x = currentWindowX + 10
    y = currentWindowY + 10
  }

  let win: BrowserWindow | null = new BrowserWindow({
    x,
    y,
    show: false,
    minWidth: 800,
    minHeight: 600,
    icon: 'public/favicon.ico',
    webPreferences: {
      nodeIntegration: true,
    },
  })

  if (process.env.WEBPACK_DEV_SERVER_URL) {
    // 开发模式下
    win.loadURL(process.env.WEBPACK_DEV_SERVER_URL as string)
    if (!process.env.IS_TEST) win.webContents.openDevTools()
  } else {
    // 非开发模式
    createProtocol('app')
    win.loadURL('app://./index.html')
  }

  // 当窗口准备好显示时，进行显示
  win.once('ready-to-show', () => {
    win && win.show()
  })

  // 窗口获得焦点时，更新菜单
  win.on('focus', createApplicationMenu)

  win.on('close', event => {
    event.preventDefault()
    win && win.webContents.send('close')
  })

  win.on('closed', () => {
    if (win) {
      windows.delete(win)
      // createApplicationMenu()
      stopWatchingFile(win)
      win = null
    }
  })

  windows.add(win)
  return win
}

// 所有窗口都关闭时退出程序
app.on('window-all-closed', () => {
  if (process.platform == 'darwin') {
    return false // 返回 false 以防止默认操作
  }
  app.quit()
})

// activate 事件只在 macOS 上触发
// 如果激活时没有窗口，则创建一个窗口
app.on('activate', (event, hasVisibleWindows) => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (!hasVisibleWindows) {
    createWindow()
  }
})

app.on('ready', async () => {
  if (isDevelopment && !process.env.IS_TEST) {
    // 开发模式
    // Install Vue Devtools
    // Devtools extensions are broken in Electron 6.0.0 and greater
    // See https://github.com/nklayman/vue-cli-plugin-electron-builder/issues/378 for more info
    // Electron will not launch with Devtools extensions installed on Windows 10 with dark mode
    // If you are not using Windows 10 dark mode, you may uncomment these lines
    // In addition, if the linked issue is closed, you can upgrade electron and uncomment these lines
    try {
      await installVueDevtools()
    } catch (e) {
      console.error('Vue Devtools failed to install:', e.toString())
    }
  } else {
    // 测试或生产模式下
    // Menu.setApplicationMenu(null)
  }

  createWindow()
  createApplicationMenu()
})

// 在应用完全启动之后，再侦听 open-file 事件
// app.on('will-finish-launching', () => {
//   // 当一个文件在应用之外被打开时，会触发 open-file 内置事件
//   app.on('open-file', (event, file) => {
//     const win = createWindow()
//     win.once('ready-to-show', () => {
//       // openFile(win, file)
//     })
//   })
// })

/**
 * 通过打开文件对话框让用户打开文件并获取其内容
 * @param {BrowserWindow} targetWindow 窗口
 * @param {string=} filename 指定的文件名
 * @return {Promise<void|IFileContent>}
 */
function getFileFromUser(targetWindow: BrowserWindow, filename?: string) {
  return new Promise<string>((resolve, reject) => {
    if (filename) {
      resolve(filename)
    } else {
      dialog.showOpenDialog(
        targetWindow,
        {
          properties: ['openFile'],
          filters: [
            // 文件过滤
            { name: 'Markdown 文件', extensions: ['md', 'markdown'] },
            { name: '文本文档', extensions: ['txt'] },
          ],
        },
        files => {
          resolve(files ? files[0] : '')
        }
      )
    }
  })
    .then(filePath => {
      if (filePath) {
        return openFile(filePath)
      } else {
        throw new Error('filePath')
      }
    })
    .then(file => {
      // app.addRecentDocument(file.path) // 将路径添加到系统“最近打开文件”中
      // targetWindow.setRepresentedFilename(file.path) // 设置窗口所代表的文件的路径名，并且将这个文件的图标放在窗口标题栏上[macOS]
      // 通过 "file-opened" 通道将文件的名称及其内容发送到渲染器进程
      // targetWindow.webContents.send('file-opened', file.path, file.content)
      startWatchingFile(targetWindow, file.path)
      return file
    })
    .catch(e => {
      if (e.message != 'filePath') {
        console.error(e)
      }
    })
}

/**
 * 读取文件内容
 * @param filePath 文件路径
 * @return {Promise<IFileContent>} 文件路径和内容
 */
function openFile(filePath: string) {
  return new Promise<IFileContent>((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) return reject(err)
      resolve({
        path: filePath,
        content: data.toString(),
      })
    })
  })
}

/**
 * 将文本保存为 HTML 文档
 * @param targetWindow 窗口
 * @param content 内容
 */
function saveHtml(targetWindow: BrowserWindow, content: string) {
  dialog.showSaveDialog(
    targetWindow,
    {
      title: '保存 HTML',
      defaultPath: app.getPath('documents'),
      filters: [
        {
          name: 'HTML Files',
          extensions: ['html', 'htm'],
        },
      ],
    },
    file => {
      if (file) {
        fs.writeFileSync(file, content)
      }
    }
  )
}

/**
 * 保存 Markdown 到文件
 * @param targetWindow 窗口
 * @param filePath 文件路径
 * @param content 文件内容
 * @return {Promise<string|undefined>} 保存文件的路径
 */
function saveMarkdown(
  targetWindow: BrowserWindow,
  filePath: string | null,
  content: string
) {
  return new Promise<string | undefined>((resolve, reject) => {
    if (filePath) {
      resolve(filePath)
    } else {
      dialog.showSaveDialog(
        targetWindow,
        {
          title: '保存 Markdown',
          defaultPath: app.getPath('documents'),
          filters: [{ name: 'Markdown 文件', extensions: ['md', 'markdown'] }],
        },
        filePath => {
          resolve(filePath)
        }
      )
    }
  }).then(filePath => {
    if (filePath) {
      fs.writeFileSync(filePath, content)
      return filePath
    }
  })
}

/**
 * 开始监控文件变动
 * @param targetWindow 窗口
 * @param filePath 文件路径
 */
function startWatchingFile(targetWindow: BrowserWindow, filePath: string) {
  stopWatchingFile(targetWindow)
  const watcher = fs.watch(filePath, eventType => {
    if (eventType == 'change') {
      const content = fs.readFileSync(filePath).toString()
      targetWindow.webContents.send('file-changed', filePath, content)
    }
  })
  openFiles.set(targetWindow, watcher)
}

/**
 * 停止监控文件变动
 * @param targetWindow 窗口
 */
function stopWatchingFile(targetWindow: BrowserWindow) {
  if (openFiles.has(targetWindow)) {
    // openFiles.get(targetWindow).stop()
    // openFiles.delete(targetWindow)
  }
}

// 创建新的窗口
ipcMain.on('create-window', (event: Event) => {
  createWindow()
})

// 打开 markdown 文件
ipcMain.on('open-file', async (event: any) => {
  try {
    const file = await getFileFromUser(
      BrowserWindow.fromWebContents(event.sender)
    )
    if (file) {
      event.reply('file-opened', file.path, file.content)
    }
  } catch (e) {
    console.error(e)
  }
})

// 读取 markdown 文件
// note 必须包含有效的 filePath
ipcMain.on('read-file', async (event: any, note: Note) => {
  try {
    const file = await getFileFromUser(
      BrowserWindow.fromWebContents(event.sender),
      note.filePath as string
    )
    event.reply('file-read', note.id, file && file.content)
  } catch (e) {
    console.error(e)
  }
})

// 保存 markdown 文件
ipcMain.on('save-markdown', async (event: any, note: Note) => {
  try {
    const filePath = await saveMarkdown(
      BrowserWindow.fromWebContents(event.sender),
      note.filePath,
      note.content
    )

    if (filePath) {
      event.reply('file-saved', note.id, filePath)
    }
  } catch (e) {
    console.error(e)
  }
})

// 保存 HTML 文件
ipcMain.on('save-html', (event: any, content: string) => {
  saveHtml(BrowserWindow.fromWebContents(event.sender), content)
})

// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
  if (process.platform === 'win32') {
    process.on('message', data => {
      if (data === 'graceful-exit') {
        app.quit()
      }
    })
  } else {
    process.on('SIGTERM', () => {
      app.quit()
    })
  }
}

export { getFileFromUser, createWindow, saveHtml, saveMarkdown }
